package com.lagou.framework.ioc;

import com.lagou.framework.annotation.*;
import com.lagou.framework.mvc.HandlerMappingResolver;
import com.lagou.framework.mvc.MethodHandler;
import org.junit.Assert;

import java.io.File;
import java.net.URL;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;

import static com.lagou.framework.constant.FileConstant.CLASS_SUFFIX;
import static com.lagou.framework.constant.FileConstant.EMPTY;

/**
 * @author srh
 * @date 2020/03/06
 **/
public class AnnotationConfigApplicationContext extends AbstractApplicationContext {

    private String basePackage;
    private ClassLoader classLoader;
    private BeanDefinitionBuilder definitionBuilder;

    private Set<MethodHandler> handlers;

    public AnnotationConfigApplicationContext() {
        super();
    }

    public void scanPackage(String basePackage) {
        // 1. 初始化beanDefinition
        scanPackage(basePackage, this::classFilter);
        // 2. 初始化handlerMapping
        HandlerMappingResolver mappingResolver = new HandlerMappingResolver();
        mappingResolver.initHandlerMapping(singletonObjects);
        this.handlers = mappingResolver.getHandlerMapping();
    }

    public Set<MethodHandler> getHandlers() {
        return handlers;
    }

    public void scanPackage(String basePackage, Predicate<Class<?>> classFilter) {
        this.basePackage = basePackage;
        this.classLoader = Thread.currentThread().getContextClassLoader();
        this.definitionBuilder = new BeanDefinitionBuilder(classLoader, classFilter);

        String rootPath = basePackage.replaceAll("\\.", "/");
        URL rootUrl = classLoader.getResource(rootPath);
        scanPackage(rootUrl, classFilter);
    }

    private void scanPackage(URL rootUrl, Predicate<Class<?>> filter) {
        Assert.assertNotNull(rootUrl);
        File file = new File(rootUrl.getFile());
        Arrays.stream(Objects.requireNonNull(file.listFiles())).forEach(f -> {
            if (f.isDirectory()) {
                scanDirectory(f);
            } else if (isClass(f.getName())) {
                scanClass(f);
            }
        });

        // build finish
        definitionBuilder.buildLazyBeanDefinitions();
        Map<String, BeanDefinition> definitionMap = definitionBuilder.getDefinitionMap();

        // 创建springBean
        preInstantiateSingletons(definitionMap);
    }

    private boolean classFilter(Class<?> clazz) {
        // 内部类过滤
        if (clazz.isAnonymousClass()) {
            return true;
            // 无自定义注解的类过滤
        } else {
            return !matchAnyAnnotation(clazz);
        }
    }

    private boolean matchAnyAnnotation(Class<?> clazz) {
        boolean match = false;
        if (null != clazz.getAnnotation(Controller.class)) {
            match = true;
        }
        if (null != clazz.getAnnotation(Autowired.class)) {
            match = true;
        }
        if (null != clazz.getAnnotation(Component.class)) {
            match = true;
        }
        if (null != clazz.getAnnotation(Service.class)) {
            match = true;
        }
        if (null != clazz.getAnnotation(Repository.class)) {
            match = true;
        }
        return match;
    }

    private boolean isClass(String name) {
        return name.endsWith(CLASS_SUFFIX);
    }

    private void scanClass(File file) {
        if (file.isDirectory()) {
            scanDirectory(file);
        } else {
            doScanClass(file);
        }
    }

    /**
     * 构建BeanDefinition
     *
     * @param file class对象
     */
    private void doScanClass(File file) {
        // 将根据绝对路径 -> 转换成类的全类名
        final String filePathWithDot = file.getAbsolutePath().replace(File.separator, ".");
        int packageIndex = filePathWithDot.indexOf(basePackage);
        String className = filePathWithDot.substring(packageIndex).replace(CLASS_SUFFIX, EMPTY);
        // 将bean的定义信息放在容器list容器中
        definitionBuilder.build(className);
    }

    private void scanDirectory(File file) {
        Arrays.stream(Objects.requireNonNull(file.listFiles())).forEach(this::scanClass);
    }

}
